#ifndef WAVE_MAP_HPP
#define WAVE_MAP_HPP

#include <algorithm>
#include <stdexcept>
#include <sstream>
#include <set>
#include "bitmap.hpp"
#include "weightmap.hpp"

class wavemap
{
public:
  typedef unsigned char field_t;
  typedef std::set<location> target_set;
  
  wavemap()
    : _wavemap(0)
    , _size(0)
    , _bm(0)
    , _rows(0)
    , _cols(0)
    , _wave(0)
    , _ready(false)
    
  {
  }

  ~wavemap()
  {
    if ( !_wavemap )
      delete[] _wavemap;
  }

  void create(int r, int c, const bitmap* bm)
  {
    _rows = r;
    _cols = c;
    _size = _rows * _cols;
    if ( _wavemap )
      delete[] _wavemap;
    _wavemap = new field_t[_size];
    if ( _wavemap )
      std::fill(_wavemap, _wavemap + _size, 255);
    _bm = bm;
    _ready = false;
    _wave = 0;
    _targets.create(r, c, 0);
  }
  
  void create(location loc, bitmap* bm)
  {
    return create(loc.row, loc.col, bm);
  }

  void reset()
  {
    _ready = false;
    _wave = 0;

    if ( !_bm )
      return;
    
    for ( int i = 0; i < _rows; ++i)
    {
      for ( int j = 0; j < _cols; ++j)
      {
        this->set_value( location(i, j), 255 );
      }
    }
  }
  
  void set( location loc, int value, int depth )
  {
    if ( depth == 0)
      return;
    
    if ( get(loc) <= value )
      return;
    
    if ( !set_value(loc, value) )
      return;
    
    for (int i = 0; i < my::TDIRECTIONS; ++i)
      set( move(loc, i), value + 1, depth-1 );
    
  }
  
  
  void nears(location loc, int value, const bitmap& targets, int depth, std::vector<item>& nrs)
  {
    
    if ( depth == 0)
      return;
    
    field_t weight = get(loc);
    if ( weight == 255 || weight!=value)
    {
      std::cerr << "stop near " << weight << " valeu=" << value << std::endl;
      return;
    }
    
    if (targets.get(loc))
    {
      std::cerr << "cautch near " << weight<< std::endl;
      nrs.push_back( item(loc,  weight) );
    }

    for (int i = 0; i < my::TDIRECTIONS; ++i)
      nears( move(loc, i), value + 1, targets, depth-1, nrs );

  }

  bool set_value( location loc, field_t val)
  {
    if ( !_wavemap )
      return false;
    
    if ( _bm && _bm->get(loc) )
      return false;

    size_t i = loc.row * _cols + loc.col;

    if (i >= _size)
      throw std::out_of_range("wavemap::set_value");

    if ( _wavemap[ i ] == val )
      return false;

    _wavemap[ i ] = val;
    return true;
  }
  
  void set( location loc )
  {
    if ( !_targets.get(loc) )
    {
      _targets.set(loc);
      _wave = 0;
      _ready = false;
    }
  }

  void set( const bitmap& targets)
  {
    if ( _targets!=targets )
      this->reset();

    _targets=targets;
    /*
    _targets=targets;

    _targets |= *_bm;

    if ( _targets!=targets )
    {
      this->reset();
      _targets=targets;
    }
    */

  }
  
  field_t get( location loc ) const
  {
    if ( !_bm->get(loc) )
      return _wavemap[ loc.row * _cols + loc.col ];

    return 255;
  }
  
  location move( location loc, int d ) const
  {
    return location::move( loc, d, location(_rows, _cols) );
  }
  
  location target( location loc)
  {
    while( get(loc) != 1)
      loc = location::move( loc, direction(loc), location(_rows, _cols) );
    return loc;
  }
  
  
  int direction( location loc )
  {
    int d = -1;
    field_t w = 255;
    for (int i = 0; i < my::TDIRECTIONS; ++i)
    {
      field_t cw = this->get( this->move( loc, i) );
      
      if (cw < w )  
      {
        w = cw; d  = i;
      }
    }
    
    return d;
  }


  int r_direction( location loc )
  {
    int d = -1;
    field_t w = 0;
    for (int i = 0; i < my::TDIRECTIONS; ++i)
    {
      field_t cw = this->get( this->move( loc, i) );
      if ( cw!=255 && cw > w)  
      {
        w = cw; d  = i;
      }
    }
    return d;
  }
  
  bool wave( location loc )
  {
    if ( this->get(loc) != _wave )
      return false;

    bool flag = false;
    for (int i = 0; i < my::TDIRECTIONS; ++i)
    {
      location wloc = this->move(loc, i);
      if ( this->get(wloc) > _wave + 1 )
        flag |= this->set_value(wloc, _wave + 1);
    }
    return flag;
  }
  
  operator bool () const
  {
    return _ready;
  }
  
  // Устанавливаем начальные точки
  int begin_drive()
  {
    _wave = 0;
    _ready = false;
    int count = 0;
    for ( int i = 0; i < _rows; ++i)
    {
      for ( int j = 0; j < _cols; ++j)
      {
        if ( _targets.get( location(i, j) ) )
        {
          count += set_value( location(i, j), 1 );
        }
      }
    }
    _ready = (count==0);
    return _wave;
    
  }
  
  int drive_wave()
  {
    
    ++_wave;

    int count = 0;

    for ( int i = 0; i < _rows; ++i)
    {
      for ( int j = 0; j < _cols; ++j)
      {
        count += wave( location(i, j) );
      }
    }

    if ( _ready && count == 0)
      --_wave;
    _ready = (count == 0);
    
    return _wave;
  }

  void show(std::ostream& os, std::string text = "")
  {
    os << "--- wave = " << _wave << " ready=" << _ready << " -- "<< text<< "------------------ " << std::endl;
    for ( int i = 0; i < _rows; ++i)
    {
      for ( int j = 0; j < _cols; ++j)
      {
        location loc(i, j);
        int val = this->get( loc );
        /*if ( _targets.get(loc) )
          os << '@';
        else */if ( val == 255 )
        {
          if ( _bm->get(loc))
            os << '#';
          else
            os << '?';
        }
        else if ( val > 9)
          os << '^';
        else
          os << val;
      }
      os << std::endl;
    }
    os << std::endl;
   }

private:
  field_t* _wavemap;
  size_t _size;
  // Карта доступных точек
  const bitmap* _bm;
  bitmap _targets;
  int _rows;
  int _cols;
  // Текущая волна
  int _wave;
  bool _ready;
};


#endif

